package invoking

import (
	"awesomeProject1/pkg/chain/ethclient"
	"awesomeProject1/pkg/e"
	"awesomeProject1/services/contract/contract"
	"context"
	"errors"
	"github.com/simplechain-org/client/log"
	"github.com/simplechain-org/go-simplechain/accounts/abi/bind"
	"github.com/simplechain-org/go-simplechain/accounts/keystore"
	"github.com/simplechain-org/go-simplechain/common"
	"github.com/simplechain-org/go-simplechain/core/types"
	"math/big"
	"strconv"
	"sync"
	"time"
)

var ErrNonceTooLow = errors.New("nonce too low")
var ErrReplacementTransactionUnderpriced = errors.New("replacement transaction underpriced")
var ErrKnownTransaction = errors.New("known transaction")
var ErrTransactionFull = errors.New("the transaction pool is full")

type service struct {
	chainClient *ethclient.Client
	chainID     *big.Int
	gasPrice    *big.Int

	pubConfigInstance *contract.PublicConfiguration

	mainAccount   *keystore.Key
	otherAccounts []*keystore.Key

	funcAccountMap  map[string]*keystore.Key
	contractCallMap map[*keystore.Key]*contractCallObj
}

type contractCallObj struct {
	callOpts     bind.CallOpts
	transactOpts bind.TransactOpts
	nonce        int64
	mu           sync.Mutex
}

func New(pubConfigContractAddr common.Address,
	chainClient *ethclient.Client, gasPrice *big.Int, mainAccount *keystore.Key, otherAccounts []*keystore.Key) (
	contractServiceI Service, code int, errStr string) {
	service := new(service)
	service.chainClient = chainClient
	service.mainAccount = mainAccount
	service.otherAccounts = otherAccounts
	service.gasPrice = gasPrice

	if pubConfigContractAddr == (common.Address{}) {
		code = e.ContractAddrErr
		return
	}

	//得到金棕贷款业务合约实例
	pubConfigInstance, err := contract.NewPublicConfiguration(pubConfigContractAddr, chainClient)
	if err != nil {
		errStr = err.Error()
		code = e.ContractAddrErr
		return
	}
	service.pubConfigInstance = pubConfigInstance

	chainID, err := chainClient.ChainID(context.Background())
	if err != nil {
		errStr = err.Error()
		code = e.ChainIDReadErr
		return
	}
	service.chainID = chainID

	code, errStr = service.initialFuncAccountMap()
	if code != e.Ok {
		return
	}
	code, errStr = service.initialContractCallMap()
	if code != e.Ok {
		return
	}

	return service, e.Ok, ""
}

// initialContractCallMap 初始化账户与合约调用对象的关系
func (s *service) initialContractCallMap() (code int, errStr string) {
	{
		key := s.mainAccount
		s.contractCallMap = map[*keystore.Key]*contractCallObj{}
		s.contractCallMap[key] = &contractCallObj{}
		s.contractCallMap[key].callOpts = bind.CallOpts{
			Pending:     false,
			From:        key.Address,
			BlockNumber: nil,
			Context:     nil,
		}

		auth, err := bind.NewKeyedTransactorWithChainID(key.PrivateKey, s.chainID)
		if err != nil {
			errStr = "create a transaction signer from a single private key error:" + err.Error() +
				" account address:" + key.Address.String()
			code = e.CreateSignerErr
			return
		}
		s.contractCallMap[key].transactOpts = bind.TransactOpts{
			From:     key.Address,
			Nonce:    nil,
			Signer:   auth.Signer,
			Value:    nil,
			GasPrice: s.gasPrice,
			GasLimit: 12345678,
			Context:  nil,
			NoSend:   false,
		}
		s.contractCallMap[key].nonce = s.GetPendingNonce(key)
	}

	for _, v := range s.otherAccounts {
		s.contractCallMap[v] = &contractCallObj{}
		s.contractCallMap[v].callOpts = bind.CallOpts{
			Pending:     false,
			From:        v.Address,
			BlockNumber: nil,
			Context:     nil,
		}

		auth, err := bind.NewKeyedTransactorWithChainID(v.PrivateKey, s.chainID)
		if err != nil {
			errStr = "create a transaction signer from a single private key error:" + err.Error() +
				" account address:" + v.Address.String()
			code = e.CreateSignerErr
			return
		}
		s.contractCallMap[v].transactOpts = bind.TransactOpts{
			From:     v.Address,
			Nonce:    nil,
			Signer:   auth.Signer,
			Value:    nil,
			GasPrice: s.gasPrice,
			GasLimit: 12345678,
			Context:  nil,
			NoSend:   false,
		}
		s.contractCallMap[v].nonce = s.GetPendingNonce(v)
	}
	return
}

// initialFuncAccountMap 初始化合约接口与账户对应关系
func (s *service) initialFuncAccountMap() (code int, errStr string) {
	s.funcAccountMap = map[string]*keystore.Key{}

	s.funcAccountMap[IsTracedAccount] = s.mainAccount
	return
}

// GetPendingNonce 获取账户的nonce
func (s *service) GetPendingNonce(account *keystore.Key) (nonce int64) {
	pendingNonceResult, err := s.chainClient.PendingNonceAt(context.TODO(), account.Address)
	if err == nil {
		nonce = int64(pendingNonceResult)
	} else {
		log.Error("GetPendingNonce", "err", err.Error())
	}
	return
}

func (s *service) getContractCallOpts(funcName string) (account *keystore.Key, transactOpts bind.TransactOpts) {
	account = s.funcAccountMap[funcName]
	contractCall := s.contractCallMap[account]
	contractCall.mu.Lock()
	transactOptsP := &contractCall.transactOpts
	//transactOpts.GasLimit = 38660
	transactOptsP.Nonce = big.NewInt(contractCall.nonce)
	contractCall.nonce++
	contractCall.mu.Unlock()
	transactOpts = *transactOptsP
	return
}

func (s *service) reloadNonce(account *keystore.Key) {
	s.contractCallMap[account].nonce = s.GetPendingNonce(account)
	log.Warn("reloadNonce", "account:", account.Address.String(), "nonce", s.contractCallMap[account].nonce)
	return
}

// 统一处理合约调用请求
func (s *service) dealContractCallReq(funcName string, params []string) (tx *types.Transaction, err error) {
	//调用合约
	tx, err = s.callContract(funcName, params)
	if err == nil {
		return
	}
	//如果失败尝试重发交易(最多两次)
	for tryCount := 1; tryCount <= 2; tryCount++ {
		tryCountStr := strconv.Itoa(tryCount)
		time.Sleep(time.Second * time.Duration(tryCount))
		switch err.Error() {
		case ErrNonceTooLow.Error(), ErrKnownTransaction.Error(), ErrReplacementTransactionUnderpriced.Error():
			//调用合约
			tx, err = s.callContract(funcName, params)
			if err != nil {
				log.Error("dealContractCallError:(第"+tryCountStr+"次重试失败)", "err", err.Error(), "funcName", funcName, "params", params)

			} else {
				log.Info("dealContractCallError:(第"+tryCountStr+"次重试成功)", "funcName", funcName, "params", params)
			}

		case ErrTransactionFull.Error():
			//等待一会儿，还是原交易，一共重试10次
			for i := 1; ; i++ {
				time.Sleep(time.Millisecond * time.Duration(200*i*tryCount))
				//调用合约
				tx, err = s.callContract(funcName, params)
				if err != nil {
					log.Error("dealContractCallError:(第"+tryCountStr+"次重试中)", "当前次数", i, "err", err.Error(), "funcName", funcName, "params", params)
					if err.Error() != ErrTransactionFull.Error() {
						log.Error("dealContractCallError:(第"+tryCountStr+"次重试失败）", "当前次数", i, "err", err.Error(), "funcName", funcName, "params", params)
						break
					}
				}
				if err == nil {
					//成功
					log.Info("dealContractCallError:(第"+tryCountStr+"次重试成功）", "当前次数", i, "funcName", funcName, "params", params)
					break
				}
				if i > 10 {
					//失败
					log.Error("dealContractCallError:(第"+tryCountStr+"次重试失败）", "当前次数", i, "err", err.Error(), "funcName", funcName, "params", params)
					break
				}
			}
		default:
			//调用合约
			tx, err = s.callContract(funcName, params)
			if err != nil {
				log.Error("dealContractCallError:(第"+tryCountStr+"次重试失败)", "err", err.Error(), "funcName", funcName, "params", params)
			} else {
				log.Info("dealContractCallError:(第"+tryCountStr+"次重试成功)", "funcName", funcName, "params", params)
			}
		}
		if err == nil {
			break
		}
	}

	if err != nil {
		go func(funcName string, params []string) {
			//加入合约调用失败记录表
			log.Error("dealContractCallError: 重试失败", "err", err.Error(), "funcName", funcName, "params", params)
		}(funcName, params)
	}

	return
}

// 处理合约调用请求
func (s *service) callContract(funcName string, params []string) (tx *types.Transaction, err error) {

	account := s.funcAccountMap[funcName]
	contractCall := s.contractCallMap[account]
	contractCall.mu.Lock()
	defer contractCall.mu.Unlock()
	transactOpts := contractCall.transactOpts
	//transactOpts.GasLimit = 38660
	transactOpts.Nonce = big.NewInt(contractCall.nonce)

	tx, err = s.sendContractCallTransaction(funcName, &transactOpts, params)
	if err != nil {
		//重置nonce
		s.reloadNonce(account)
		switch err.Error() {
		case ErrNonceTooLow.Error():
			transactOpts.Nonce = big.NewInt(contractCall.nonce)
			//再试一次
			tx, err = s.sendContractCallTransaction(funcName, &transactOpts, params)
		case ErrKnownTransaction.Error(), ErrReplacementTransactionUnderpriced.Error():
			time.Sleep(time.Millisecond * 200)
			transactOpts.Nonce = big.NewInt(contractCall.nonce)
			//再试一次
			tx, err = s.sendContractCallTransaction(funcName, &transactOpts, params)
		}
	}

	//如果调用合约成功了，nonce+1
	if err == nil {
		//更新nonce
		contractCall.nonce++
	}
	return
}

//发送合约调用交易
func (s *service) sendContractCallTransaction(funcName string, transactOptsP *bind.TransactOpts, params []string) (tx *types.Transaction, err error) {
	switch funcName {
	default:
		err = errors.New("callContractAgain:" + funcName + ",未实现该合约的调用。")
	}
	return
}

// Config 解析配置文件里的配置
type Config struct {
	ContractAddr string `yaml:"contractAddr"`
}
